 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.texteditor.rulers;

 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.Collections ;
 import java.util.Comparator ;
 import java.util.HashMap ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.ListIterator ;
 import java.util.Map ;
 import java.util.Set ;

 import com.ibm.icu.text.MessageFormat;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtensionRegistry;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.InvalidRegistryObjectException;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;

 import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
 import org.eclipse.ui.internal.texteditor.rulers.DAG;
 import org.eclipse.ui.internal.texteditor.rulers.ExtensionPointHelper;
 import org.eclipse.ui.internal.texteditor.rulers.RulerColumnMessages;
 import org.eclipse.ui.internal.texteditor.rulers.RulerColumnPlacementConstraint;
 import org.eclipse.ui.texteditor.ConfigurationElementSorter;

 /**
  * A registry for all extensions to the
  * <code>rulerColumns</code> extension point.
  *
  * @since 3.3
  */
 public final class RulerColumnRegistry {

     private static final String EXTENSION_POINT= "rulerColumns"; //$NON-NLS-1$
 private static final String QUALIFIED_EXTENSION_POINT= TextEditorPlugin.PLUGIN_ID + '.' + EXTENSION_POINT;
     
     /** The singleton instance. */
     private static RulerColumnRegistry fgSingleton= null;
     
     /**
      * Returns the default computer registry.
      * <p>
      * TODO keep this or add some other singleton, e.g. TextEditorPlugin?
      * </p>
      *
      * @return the singleton instance
      */
     public static synchronized RulerColumnRegistry getDefault() {
         if (fgSingleton == null) {
             fgSingleton= new RulerColumnRegistry();
         }
         
         return fgSingleton;
     }
     
     /**
      * All descriptors (element type:
      * {@link RulerColumnDescriptor}).
      */
     private List fDescriptors= null;
     /**
      * All descriptors by id (element type: {@link RulerColumnDescriptor}).
      */
     private Map fDescriptorMap= null;
     
     /**
      * <code>true</code> if this registry has been loaded.
      */
     private boolean fLoaded= false;

     /**
      * Creates a new instance.
      */
     RulerColumnRegistry() {
     }

     /**
      * Returns the list of {@link RulerColumnDescriptor}s describing all extensions to the
      * <code>rulerColumns</code> extension point. The list's iterator traverses the descriptors in
      * the ordering implied by the placement specifications of the contributions.
      * <p>
      * The returned list is unmodifiable and guaranteed to never change. Note that the set of
      * descriptors may change over time due to dynamic plug-in removal or addition.
      * </p>
      *
      * @return the sorted list of extensions to the <code>rulerColumns</code> extension point
      * (element type: {@link RulerColumnDescriptor})
      */
     public List getColumnDescriptors() {
         ensureExtensionPointRead();
         return fDescriptors;
     }
     
     /**
      * Returns the {@link RulerColumnDescriptor} with the given identity, <code>null</code> if no
      * such descriptor exists.
      *
      * @param id the identity of the ruler contribution as given in the extension point xml.
      * @return the {@link RulerColumnDescriptor} with the given identity, <code>null</code> if no
      * such descriptor exists
      */
     public RulerColumnDescriptor getColumnDescriptor(String id) {
         Assert.isLegal(id != null);
         ensureExtensionPointRead();
         return (RulerColumnDescriptor) fDescriptorMap.get(id);
     }

     /**
      * Ensures that the extensions are read and stored in
      * <code>fDescriptorsByPartition</code>.
      */
     private void ensureExtensionPointRead() {
         boolean reload;
         synchronized (this) {
             reload= !fLoaded;
             fLoaded= true;
         }
         if (reload)
             reload();
     }

     /**
      * Reloads the extensions to the extension point.
      * <p>
      * This method can be called more than once in order to reload from
      * a changed extension registry.
      * </p>
      */
     public void reload() {
         IExtensionRegistry registry= Platform.getExtensionRegistry();
         List elements= new ArrayList (Arrays.asList(registry.getConfigurationElementsFor(TextEditorPlugin.PLUGIN_ID, EXTENSION_POINT)));
         
         List descriptors= new ArrayList ();
         Map descriptorMap= new HashMap ();
         
         for (Iterator iter= elements.iterator(); iter.hasNext();) {
             IConfigurationElement element= (IConfigurationElement) iter.next();
             try {
                 RulerColumnDescriptor desc= new RulerColumnDescriptor(element, this);
                 String id= desc.getId();
                 if (descriptorMap.containsKey(id)) {
                     noteDuplicateId(desc);
                     continue;
                 }
                     
                 descriptors.add(desc);
                 descriptorMap.put(id, desc);
             } catch (InvalidRegistryObjectException x) {
                 /*
                  * Element is not valid any longer as the contributing plug-in was unloaded or for
                  * some other reason. Do not include the extension in the list and inform the user
                  * about it.
                  */
                 noteInvalidExtension(element, x);
             }
         }
         
         sort(descriptors);
         
         synchronized (this) {
             fDescriptors= Collections.unmodifiableList(descriptors);
             fDescriptorMap= Collections.unmodifiableMap(descriptorMap);
         }
     }

     /**
      * Sorts the column contributions.
      *
      * @param descriptors the descriptors to sort
      */
     private void sort(List descriptors) {
         /*
          * Topological sort of the DAG defined by the plug-in dependencies
          * 1. TopoSort descriptors by plug-in dependency
          * 2. Insert into Directed Acyclic Graph
          * 3. TopoSort DAG: pick the source with the lowest gravity and remove from DAG
          */
         ConfigurationElementSorter sorter= new ConfigurationElementSorter() {
             public IConfigurationElement getConfigurationElement(Object object) {
                 return ((RulerColumnDescriptor) object).getConfigurationElement();
             }
         };
         Object [] array= descriptors.toArray();
         sorter.sort(array);
         
         Map descriptorsById= new HashMap ();
         for (int i= 0; i < array.length; i++) {
             RulerColumnDescriptor desc= (RulerColumnDescriptor) array[i];
             descriptorsById.put(desc.getId(), desc);
         }
         
         DAG dag= new DAG();
         for (int i= 0; i < array.length; i++) {
             RulerColumnDescriptor desc= (RulerColumnDescriptor) array[i];
             dag.addVertex(desc);
             
             Set before= desc.getPlacement().getConstraints();
             for (Iterator it= before.iterator(); it.hasNext();) {
                 RulerColumnPlacementConstraint constraint= (RulerColumnPlacementConstraint) it.next();
                 String id= constraint.getId();
                 RulerColumnDescriptor target= (RulerColumnDescriptor) descriptorsById.get(id);
                 if (target == null) {
                     noteUnknownTarget(desc, id);
                 } else {
                     boolean success;
                     if (constraint.isBefore())
                         success= dag.addEdge(desc, target);
                     else
                         success= dag.addEdge(target, desc);
                     if (!success)
                         noteCycle(desc, target);
                 }
             }
         }
         
         Comparator gravityComp= new Comparator () {
             public int compare(Object o1, Object o2) {
                 float diff= ((RulerColumnDescriptor) o1).getPlacement().getGravity() - ((RulerColumnDescriptor) o2).getPlacement().getGravity();
                 if (diff == 0)
                     return 0;
                 if (diff < 0)
                     return -1;
                 return 1;
             }
         };
         
         /* Topological sort - always select the source with the least gravity */
         Set toProcess= dag.getSources();
         int index= 0;
         while (!toProcess.isEmpty()) {
             Object next= Collections.min(toProcess, gravityComp);
             array[index]= next;
             index++;
             dag.removeVertex(next);
             toProcess= dag.getSources();
         }
         Assert.isTrue(index == array.length);
         
         ListIterator it= descriptors.listIterator();
         for (int i= 0; i < index; i++) {
             it.next();
             it.set(array[i]);
         }
     }

     private void noteInvalidExtension(IConfigurationElement element, InvalidRegistryObjectException x) {
         String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_invalid_msg, new Object [] {ExtensionPointHelper.findId(element)});
         warnUser(message, x);
     }

     private void noteUnknownTarget(RulerColumnDescriptor desc, String referencedId) {
         String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_unresolved_placement_msg, new Object [] {QUALIFIED_EXTENSION_POINT, referencedId, desc.getName(), desc.getContributor()});
         warnUser(message, null);
     }

     private void noteCycle(RulerColumnDescriptor desc, RulerColumnDescriptor target) {
         String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_cyclic_placement_msg, new Object [] {QUALIFIED_EXTENSION_POINT, target.getName(), desc.getName(), desc.getContributor()});
         warnUser(message, null);
     }

     private void noteDuplicateId(RulerColumnDescriptor desc) {
         String message= MessageFormat.format(RulerColumnMessages.RulerColumnRegistry_duplicate_id_msg, new Object [] {QUALIFIED_EXTENSION_POINT, desc.getId(), desc.getContributor()});
         warnUser(message, null);
     }

     private void warnUser(String message, Exception exception) {
         IStatus status= new Status(IStatus.WARNING, TextEditorPlugin.PLUGIN_ID, IStatus.OK, message, exception);
         TextEditorPlugin.getDefault().getLog().log(status);
     }
 }

